package ca.swank.hardquor;

import java.net.*;
import java.io.*;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.HashMap;

/**
* Provides communication between a hardqour game server and an implementation
* of the <CODE>hardquorUser<CODE> interface. To use this class:
* <OL>
*   <LI>develop an implementation of the <CODE>hardquorUser</CODE> interface.
*   <LI>construct a <CODE>hardquorClient</CODE> object from the <CODE>hardquorUser</CODE>
*       implementation. Use the current <CODE>hardquorUser</CODE> instance as the construction
*       parameter.
*   <LI>Respond to user actions in your <CODE>hardquorUser</CODE> implementation by calling
*       methods on the <CODE>hardquorClient</CODE> object.
*   <LI>Respond to methods of the <CODE>hardquorUser</CODE> interface with appropriate
*       actions.
* </OL>
*
* @author Jesse Wilson
* @version 1.0 9/23/2001
*/
public class hardquorClient implements Runnable
{
  private hardquorUser user;

  private BufferedWriter out;
  private BufferedReader in;
  private Socket connection;

  private boolean connected;

  private HashMap userGames;

  /**
  * create a hardquorClient object for communication between a <CODE>hardquorUser</CODE>
  * implementation and a hardquor game server.
  *
  * @param user the <CODE>hardquorUser</CODE> instance that will respond to instructions,
  *        commands and information from the hardquor game server.
  */
  public hardquorClient(hardquorUser user)
  { this.user = user;

    out = null;
    in = null;
    connection = null;
    connected = false;

    userGames = new HashMap();
  }

  /**
  * connects to the hardquor game server running on the specified IP. A connection
  * must be established before any other game or user methods may be invoked.
  *
  * @param ip the IP address of a machine running a hardquor game server.
  */
  public boolean connect(String ip)
  {
    try
    { connection = new Socket(InetAddress.getByName(ip),5309);

      in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
      out = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
      connected = true;

    } catch(IOException e)
    { System.out.println("error connecting to remote server");
    }

    if(connected)
    { Thread interaction = new Thread(this);
      interaction.start();
    }

    return connected;

  }

  /**
  * disconnects from the current hardquor game server. Upon disconnecting,
  * no methods bar <CODE>connect</CODE> may be called until a connection to
  * a hardquor game server is established.
  */
  public void disconnect()
  { writeln("DISCONNECT");
    connected = false;
    close();
  }

  /**
  * an implementation specific method. Reads messages from the current
  * hardquorGameServer and processes them.
  */
  public void run()
  {

    String[] command;
    boolean connected = true;

    while(connected == true && (command = readCommand()) != null)
    {
      if(command.length == 0)
      {
	  } else if(command[0].equals("SENDUSERNAME"))
	  { handleUsernameRequestCommand(command);

	  } else if(command[0].equals("SPEAK"))
	  { handleSpeakCommand(command);

	  } else if(command[0].equals("USERNEW"))
	  { handleUserNewCommand(command);

	  } else if(command[0].equals("USERLIST"))
	  { handleUserListCommand(command);

	  } else if(command[0].equals("USERGONE"))
	  { handleUserGoneCommand(command);

	  } else if(command[0].equals("GAMEINVITE"))
	  { handleGameInviteCommand(command);

	  } else if(command[0].equals("GAMEMOVE"))
	  { handleGameMoveCommand(command);

	  } else if(command[0].equals("GAMETURN"))
	  { handleGameTurnCommand(command);

	  } else if(command[0].equals("GAMESTART"))
	  { handleGameStartCommand(command);
	  
	  } else if(command[0].equals("GAMEWINNER"))
	  { handleGameWinnerCommand(command);

	  } else if(command[0].equals("GAMEQUIT"))
	  { handleGameQuitCommand(command);
      }


    }
    close();
  }

  /**
  * attempts to provide the current hardquor game server with a username
  * to identify the current user.
  *
  * @param username a short string that other users may use to reference
  *        the current user.
  */
  public void tryUsername(String username)
  { writeln("USERNAME " + username);
  }

  /**
  * chats a message to the other users connected to the current hardquor
  * game server. These messages are public to all users connected to the
  * server and should not be used for in game conversation. The user
  * making speak commands is not annonymous.
  *
  * @param comment the message to be broadcast to others connected to the
  *        current hardquor game server.
  */
  public void speak(String comment)
  { writeln("SPEAK 0 " + comment);
  }

  /**
  * requests a game between the current player and the opponent specified.
  * The opponent specified will receive the request and have the option to
  * accept it or leave the request. No decline notification is required,
  * but the request is valid until accepted or either the request maker
  * or recipient disconnects from the hardquor game server.
  *
  * @param details information about the game request, including the target
  *        username and whether the current user wishes to be first or second
  *        player in the game.
  */
  public void makeGameRequest(hardquorGameRequest details)
  { writeln("GAMEINVITE " + details.getPlayerNumber() + " " + details.getOpponent());
  }

  /**
  * accepts a game request made on a hardquorUser instance with the
  * <CODE>notifyGameRequest</CODE> method. The parameter provided may
  * not be one constructed by the <CODE>hardquorUser</CODE> implementation,
  * but rather the instance passed as a parameter to that class with
  * the <CODE>notifyGameRequest</CODE> method. Acceptance of a valid game
  * request will start a game if both players are still active. The
  * game will be started with the <CODE>startGame</CODE> method of the
  * <CODE>hardquorUser</CODE> class.
  *
  * @param accepted an game request on which to base a new game. The instance
  *        must have been initially received as a parameter to the
  *        <CODE>notifyGameRequest</CODE> method.
  */
  public void acceptGameRequest(hardquorGameRequest accepted)
  { writeln("GAMEACCEPT " + accepted.getGameNumber());
  }

  
  public void AIUserloaded(String ainame) {
	  writeln("USERNAME " + ainame);
  }

  private void handleUsernameRequestCommand(String[] command)
  { if(command.length == 2)
    { String reason = command[1];
      user.notifyRequestUserName(reason);
    }
  }

  private void handleSpeakCommand(String[] command)
  { if(command.length >= 4)
    { int context = Integer.valueOf(command[1]).intValue();
      String username = command[2];
      String message = command[3];
      for(int i = 4; i < command.length; i++)
      { message = message + " " + command[i];
      }

      if(context == 0) user.notifySpeak(username, message);
      else
      { hardquorUserGame game = (hardquorUserGame)userGames.get(Integer.valueOf(command[1]));
        game.notifySpeak(username, message);
      }
    }
  }

  private void handleUserNewCommand(String[] command)
  { if(command.length == 2)
    { user.notifyUserNew(command[1]);
    }
  }

  private void handleUserListCommand(String[] command)
  { if(command.length >= 2)
    { String[] userList = new String[command.length - 1];
      for(int i = 0; i < userList.length; i++)
      { userList[i] = command[i+1];
      }
      user.notifyUserList(userList);
    }
  }

  private void handleUserGoneCommand(String[] command)
  { if(command.length == 2)
    { user.notifyUserGone(command[1]);
    }
  }

  private void handleGameInviteCommand(String[] command)
  { if(command.length == 4)
    { int gameNumber = Integer.valueOf(command[1]).intValue();
      int playerNumber = Integer.valueOf(command[2]).intValue();
      String opponent = command[3];
      user.notifyGameRequest(new hardquorGameRequest(playerNumber, opponent, gameNumber));
    }
  }

  private void handleGameStartCommand(String[] command)
  { if(command.length == 4)
    { int gameNumber = Integer.valueOf(command[1]).intValue();
      int playerNumber = Integer.valueOf(command[2]).intValue();
      String opponent = command[3];

      hardquorGameClient gameClient = new hardquorGameClient(this, gameNumber);
      hardquorUserGame userGame = user.startGame(gameClient, playerNumber);
      gameClient.setUserGame(userGame);

      userGames.put(new Integer(gameNumber), userGame);
    }
  }
  
  private void handleGameWinnerCommand(String[] command)
  { if(command.length == 4)
    { int gameNumber = Integer.valueOf(command[1]).intValue();
      int playerNumber = Integer.valueOf(command[2]).intValue();
      System.out.println("Winner is " + playerNumber);
    }
  }

  private void handleGameMoveCommand(String[] command)
  {
    hardquorUserGame game = null;
    if(command.length >= 2) game = (hardquorUserGame)userGames.get(Integer.valueOf(command[1]));

    if(game != null && (command.length == 4 || command.length == 6))
    {
      if(command.length == 4)
	  {
	    int[] source = breakApartCoordinates(command[2]);
	    int[] target = breakApartCoordinates(command[3]);
	    game.notifyMove(source, target);

	  } else if(command.length == 6)
	  {
	    int[] a1 = breakApartCoordinates(command[2]);
	    int[] a2 = breakApartCoordinates(command[3]);
	    int[] b1 = breakApartCoordinates(command[4]);
	    int[] b2 = breakApartCoordinates(command[5]);
	    game.notifyMove(a1, a2, b1, b2);
	  }
	}
  }


  private void handleGameTurnCommand(String[] command)
  {
    hardquorUserGame game = null;
    if(command.length >= 2) game = (hardquorUserGame)userGames.get(Integer.valueOf(command[1]));

    game.notifyTurn();
  }

  private void handleGameQuitCommand(String[] command)
  { if(command.length == 3)
    { Integer gameNumber = Integer.valueOf(command[1]);
      hardquorUserGame game = (hardquorUserGame)userGames.get(gameNumber);
      String quitter = command[2];
      game.notifyQuit(quitter);
      userGames.remove(gameNumber);
    }
  }


  private void close()
  { try
    { in.close();
      out.close();
      connection.close();
    } catch(IOException e)
    { System.out.println("error closing a connection");
    }
  }

  /**
  * an implementation specific method. Sends a command to the current hardquor
  * game server.
  */
  public void writeln(String line)
  {
    try
    { out.write(line + "\n");
      out.flush();
    } catch(IOException e)
    { System.out.println("error writing line " + line);
    }
  }

  private String readln()
  {
    try
    { String line = in.readLine();
      return line;
    } catch(IOException e)
    { System.out.println("Error reading line or raw disconnect.");
    }
    return null;
  }

  private String[] readCommand()
  { return breakApartCommand(readln());
  }

  private String[] breakApartCommand(String command)
  { if(command == null) return null;
    ArrayList commandSegments = new ArrayList();
    StringTokenizer tok = new StringTokenizer(command, " \n\t");
    while(tok.hasMoreTokens())
    { commandSegments.add(tok.nextToken());
    }
    return (String[])commandSegments.toArray(new String[] { });
  }

  private int[] breakApartCoordinates(String coordinates)
  { StringTokenizer tok = new StringTokenizer(coordinates, " ,");
    int[] coords = new int[2];
    coords[0] = Integer.valueOf(tok.nextToken()).intValue();
    coords[1] = Integer.valueOf(tok.nextToken()).intValue();
    return coords;
  }
}
